package cn.ppool.xmlmapper.builder.parse;

import cn.ppool.xmlmapper.builder.MapperBuilder;
import cn.ppool.xmlmapper.builder.annotation.*;
import cn.ppool.xmlmapper.builder.parse.model.base.BaseEntryBO;
import cn.ppool.xmlmapper.builder.parse.model.entry.*;
import cn.ppool.xmlmapper.builder.xml.XNode;
import cn.ppool.xmlmapper.builder.xml.XPathParser;
import cn.ppool.xmlmapper.exctption.NotFoundRedirectIdException;
import lombok.Getter;

import java.io.InputStream;
import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;


public class Mapper {


    public Mapper(MapperBuilder mapperBuilder) {
        this.entry = new HashMap<>();
        this.mapperBuilder = mapperBuilder;
    }

    @Getter
    private String namespace;
    @Getter
    private Class<?> proxyClass;
    @Getter
    private final MapperBuilder mapperBuilder;

    private final Map<String, BaseEntryBO> entry;

    public Map<String, BaseEntryBO> getAllEntry() {
        return entry;
    }

    public BaseEntryBO getEntry(String id) {
        return entry.get(id);
    }

    public void putEntry(String id, BaseEntryBO bo) {
        entry.put(id, bo);
    }

    public boolean hasEntry(String id) {
        return entry.containsKey(id);
    }


    public BaseEntryBO build(String id, Method method, XNode rootNode, Map<String, XNode> funNodes, Map<String, Method> medList) throws Exception {
        if (hasEntry(id)) throw new RuntimeException("id is exists." + id);
        XNode funNode = funNodes.get(id);
        String func = funNode != null ? funNode.getName().toUpperCase() : null;
        //
        Select selectAnt = method.getAnnotation(Select.class);
        Update updateAnt = method.getAnnotation(Update.class);
        Insert insertAnt = method.getAnnotation(Insert.class);
        Delete deleteAnt = method.getAnnotation(Delete.class);
        Flush flushAnt = method.getAnnotation(Flush.class);
        if (selectAnt != null && funNode == null) {
            String redirectToId = selectAnt.redirectTo();
            if (!"".equals(redirectToId)) {     // 重定向截胡
                if (!medList.containsKey(redirectToId)) {
                    throw new NotFoundRedirectIdException();
                }
                SelectBO red = new SelectBO(this.mapperBuilder, rootNode, funNodes.get(redirectToId), medList.get(redirectToId), redirectToId);
                return new SelectBO(this.mapperBuilder, rootNode, funNode, method, id, red);
            }
            func = SelectBO.TAG_NAME;
        } else if (updateAnt != null && funNode == null) {
            func = UpdateBO.TAG_NAME;
        } else if (insertAnt != null && funNode == null) {
            func = InsertBO.TAG_NAME;
        } else if (deleteAnt != null && funNode == null) {
            func = DeleteBO.TAG_NAME;
        } else if (flushAnt != null && funNode == null) {
            func = FlushBO.TAG_NAME;
        }
        //
        if (func == null) throw new RuntimeException("没有找到注解和xml配置：" + id);
        switch (func) {
            case SelectBO.TAG_NAME:
                return new SelectBO(this.mapperBuilder, rootNode, funNode, method, id);
            case UpdateBO.TAG_NAME:
                return new UpdateBO(this.mapperBuilder, rootNode, funNode, method, id);
            case DeleteBO.TAG_NAME:
                return new DeleteBO(this.mapperBuilder, rootNode, funNode, method, id);
            case InsertBO.TAG_NAME:
                return new InsertBO(this.mapperBuilder, rootNode, funNode, method, id);
            case FlushBO.TAG_NAME:
                return new FlushBO(this.mapperBuilder, rootNode, funNode, method, id);
            default:
                throw new RuntimeException("不支持的tag:" + funNode.getName().toUpperCase());
        }
    }


    public void build(InputStream xml) throws Exception {
        // 读取xml
        XPathParser parse = new XPathParser(xml, false);
        XNode rootNode = parse.evalNode("/mapper");
        String namespace = rootNode.getStringAttribute("namespace");
        if (namespace == null) throw new RuntimeException("namespace is null");
        //
        this.proxyClass = Class.forName(namespace);
        this.namespace = namespace;
        //
        List<XNode> fn = rootNode.evalNodes("select|insert|update|delete");
        Map<String, XNode> funNodes = new HashMap<String, XNode>() {{
            for (XNode node : fn) {
                String id = node.getStringAttribute("id");
                if (id == null) throw new RuntimeException("id is null");
                if (containsKey(id)) throw new RuntimeException("重复id");
                put(id, node);
            }
        }};
        //
        Map<String, Method> medList = Arrays.stream(getProxyClass().getDeclaredMethods()).collect(Collectors.toMap(Method::getName, s -> s));
        for (Map.Entry<String, Method> m : medList.entrySet()) {
//            if(!m.getKey().contains("multipleConditionBreak")) continue;
            putEntry(m.getKey(), build(m.getKey(), m.getValue(), rootNode, funNodes, medList));
        }
    }

}
